演算法中有個經典的問題:樹狀結構,要把它的每個節點都拜訪一次呢?
用途:[day-17] 的樹狀結構,走訪每個 node 就可以將每個 node 的 attrStr 轉換成 attrs
( •̀ ω •́ )✧
我們可以用 DFS - 深度優先搜尋,或是 BFS - 廣度優先搜尋,來解決這個問題。
深度優先搜尋,就是遇到 node 時,往 child node 走,一直往下走,直到走不下去為止,然後再回頭,往下一個根節點走,直到走完所有的節點。
const logger = node => {
if (node.type === 'text')
console.log('currentNode=', node.content)
else
console.log('currentNode=', node.type)
};
function dfs(ast) {
// 當前節點,第一次進入時 ast 也就是 ROOT 節點
const currentNode = ast;
// console.log 當前節點
logger(currentNode);
if (currentNode.children?.length > 0) {
// 如果當前節點有 child node,就遞迴走訪
for (const childNode of currentNode.children) {
dfs(childNode);
}
}
}
廣度優先搜尋,就是每個 node,走完當前 node 的所有的 child node,才往孫節點走,直到走完所有的節點。
const logger = node => {
if (node.type === 'text')
console.log('currentNode=', node.content)
else
console.log('currentNode=', node.type)
};
function bfs(ast) {
// 當前節點,第一次進入時 ast 也就是 ROOT 節點
const currentNode = ast;
const stack = [currentNode];
while (stack.length > 0) {
// 取出 stack 的第一個節點
const currentNode = stack.shift();
// console.log 當前節點
logger(currentNode);
if (currentNode.children?.length > 0) {
// 如果當前節點有 child node,就把 child node 放入 stack
for (const childNode of currentNode.children) {
stack.push(childNode);
}
}
}
}
NOTE: logger 這個函式每次遇到新的 node 時,都會被呼叫一次
如果將 logger 改成 transformer 的話,就可以在每次遇到一個 node 時,執行我們想要那個 node 多做的事情,例如將 attrStr 做 tokenize。
+ const transformer = require("./transformer");
function dfs(ast) {
...
// 改成 transformer
+ transformer(currentNode);
- logger(currentNode);
...
}
transformer 與 PluginManager 的實作
// pluginManager.js
class PluginManager {
plugins = [];
add(plugin) {
this.plugins.push(plugin);
}
setPlugins(plugins) {
this.plugins = plugins;
}
get() {
return this.plugins;
}
}
const pluginManager = new PluginManager();
module.exports = pluginManager;
// transformer.js
const transformer = node => {
const plugins = pluginManager.get();
for (const plugin of plugins) {
const {visitor} = plugin;
const {ALL: allHandler, [node.type]: typeHandler} = visitor;
// 先執行 ALL 通用的處理函式
allHandler && allHandler(node);
// 再執行對應 type 的處理函式
typeHandler && typeHandler(node);
}
};
我們來執行一下 O(∩_∩)O
// app.js
const AttrTokenizer = require("../day-18/attrStr-tokenizer");
const pluginManager = require("./pluginManager");
const dfs = require("./dfs");
const plugin = () => ({
visitor: {
ALL(node) {
// 所有類型的 node 都會進入此函示處理
if (node.type !== 'text' && node.attrStr) {
node.attrs = new AttrTokenizer(node.attrStr).tokenize();
}
},
text(node) {
// 只有 type = "text" 的 node 會進入此函示處理
console.log('text node=', node.content);
}
}
});
pluginManager.add(plugin);
const ast = require("./ast.json");
const newAST = dfs(ast);
console.log('newAST=', JSON.stringify(newAST, null, 2));
如果我們查看 BABEL 撰寫你的第一個 Babel 外掛 的範例,
可以看到他們是這樣寫的:
export default function ({types: t}) {
return {
visitor: {
Identifier(path, state) {
},
ASTNodeTypeHere(path, state) {
}
}
};
};
看過上面的解說,我們大致上可以理解 BABEL 的 Plugin 是如何運作的 []~( ̄▽ ̄)~*
今天我們只是大概提了一下,如何遍歷 (Traverse)樹狀結構 TREE,如果想要更詳細的說明。
可以查看這篇邦友的文章:【Day33】[演算法]-深度優先搜尋DFS與廣度優先搜尋BFS